You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
188 lines
6.1 KiB
188 lines
6.1 KiB
<script setup lang="ts">
|
|
import { HouseIcon, HandbagIcon } from 'lucide-vue-next'
|
|
|
|
/**
|
|
* Order Success Page (/order/success/[orderId])
|
|
*
|
|
* Features:
|
|
* - Requires authentication (middleware: auth)
|
|
* - Fetches order details from /api/orders/[orderId]
|
|
* - Validates order belongs to user (server-side)
|
|
* - Validates order status is 'completed'
|
|
* - Shows success message and animation
|
|
* - Shows order number
|
|
* - Shows OrderSummary component (read-only)
|
|
* - Links to homepage and product pages
|
|
*/
|
|
|
|
definePageMeta({
|
|
middleware: 'auth',
|
|
layout: 'default',
|
|
})
|
|
|
|
const route = useRoute()
|
|
const orderId = computed(() => route.params.orderId as string)
|
|
|
|
// Get cart composable to refresh cart state
|
|
const { fetchCart } = useCart()
|
|
|
|
// Order data
|
|
const order = ref<any>(null)
|
|
const isLoading = ref(false)
|
|
const error = ref<string | null>(null)
|
|
|
|
// Fetch order details
|
|
async function fetchOrder() {
|
|
if (!orderId.value) return
|
|
|
|
isLoading.value = true
|
|
error.value = null
|
|
|
|
try {
|
|
order.value = await $fetch(`/api/orders/${orderId.value}`)
|
|
|
|
// Check order status
|
|
if (order.value.status !== 'completed') {
|
|
error.value = `Diese Bestellung wurde noch nicht abgeschlossen. Status: ${order.value.status}`
|
|
|
|
// Redirect to confirmation page if still pending
|
|
if (order.value.status === 'pending') {
|
|
setTimeout(() => {
|
|
navigateTo(`/order/confirm/${orderId.value}`)
|
|
}, 2000)
|
|
}
|
|
} else {
|
|
// Order completed successfully - refresh cart to show it's empty
|
|
await fetchCart()
|
|
}
|
|
} catch (err: any) {
|
|
console.error('Failed to fetch order:', err)
|
|
|
|
if (err.statusCode === 404) {
|
|
error.value = 'Bestellung nicht gefunden'
|
|
} else if (err.statusCode === 403) {
|
|
error.value = 'Du hast keine Berechtigung, diese Bestellung zu sehen'
|
|
} else {
|
|
error.value = 'Fehler beim Laden der Bestellung'
|
|
}
|
|
|
|
// Redirect to homepage after 3 seconds
|
|
setTimeout(() => {
|
|
navigateTo('/')
|
|
}, 3000)
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
// Fetch order on mount
|
|
onMounted(() => {
|
|
fetchOrder()
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div>
|
|
<CommonHeader />
|
|
|
|
<div class="container mx-auto px-4 py-8 max-w-4xl">
|
|
<!-- Error Alert -->
|
|
<Alert v-if="error" variant="error" class="mb-6">
|
|
<AlertTitle>Fehler</AlertTitle>
|
|
<AlertDescription>{{ error }}</AlertDescription>
|
|
</Alert>
|
|
|
|
<!-- Loading State -->
|
|
<div v-if="isLoading" class="text-center py-12">
|
|
<div class="animate-spin rounded-full h-12 w-12 border-4 border-white/20 border-t-white mx-auto mb-4" />
|
|
<p class="text-white/60">Lade Bestellung...</p>
|
|
</div>
|
|
|
|
<!-- Success Content -->
|
|
<div v-else-if="order && order.status === 'completed'" class="space-y-8">
|
|
<!-- Success Header with Animation -->
|
|
<div class="text-center space-y-4 py-8">
|
|
<!-- Success Icon (animated checkmark) -->
|
|
<div class="flex justify-center mb-6">
|
|
<div class="rounded-full bg-green-500/20 p-6 border-4 border-green-500/50 animate-pulse">
|
|
<svg class="w-16 h-16 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"
|
|
xmlns="http://www.w3.org/2000/svg">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Success Message -->
|
|
<h1 class="text-4xl font-bold text-white mb-2">
|
|
Vielen Dank für deine Bestellung!
|
|
</h1>
|
|
<p class="text-xl text-white/70">Deine Bestellung wurde erfolgreich abgeschlossen.</p>
|
|
|
|
<!-- Order Number -->
|
|
<div class="inline-block mt-4 px-6 py-3 bg-white/5 rounded-lg border border-white/10">
|
|
<p class="text-sm text-white/60">Bestellnummer</p>
|
|
<p class="text-2xl font-mono font-bold text-experimenta-accent">
|
|
{{ order.orderNumber }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Next Steps Info -->
|
|
<Alert class="border-blue-500/50 bg-blue-500/10">
|
|
<div class="flex items-start gap-3">
|
|
<svg class="w-5 h-5 text-blue-400 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor"
|
|
viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
|
</svg>
|
|
<div>
|
|
<AlertTitle class="text-blue-400">Wie geht es weiter?</AlertTitle>
|
|
<AlertDescription class="text-blue-100/90 space-y-2">
|
|
<p>
|
|
Du erhältst in Kürze eine Bestätigungs-E-Mail mit allen Details zu deiner
|
|
Bestellung.
|
|
</p>
|
|
<p>
|
|
Deine Makerspace-Jahreskarte wird bearbeitet und steht dir bald zur
|
|
Verfügung.
|
|
</p>
|
|
</AlertDescription>
|
|
</div>
|
|
</div>
|
|
</Alert>
|
|
|
|
<!-- Order Summary Card -->
|
|
<Card class="p-6">
|
|
<OrderSummary :order="order" :show-address="true" />
|
|
</Card>
|
|
|
|
<!-- Action Buttons -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<NuxtLink to="/" class="w-full">
|
|
<Button variant="secondary" size="experimenta" class="w-full">
|
|
<HouseIcon class="w-5 h-5 mr-2" />
|
|
Zurück zur Startseite
|
|
</Button>
|
|
</NuxtLink>
|
|
|
|
<NuxtLink to="/experimenta" class="w-full">
|
|
<Button variant="experimenta" size="experimenta" class="w-full">
|
|
<HandbagIcon class="w-5 h-5 mr-2" />
|
|
Weitere Produkte kaufen
|
|
</Button>
|
|
</NuxtLink>
|
|
</div>
|
|
|
|
<!-- Support Info -->
|
|
<div class="text-center pt-4 space-y-2">
|
|
<p class="text-sm text-white/60">
|
|
Fragen zu deiner Bestellung? Kontaktiere uns gerne:
|
|
</p>
|
|
<a href="mailto:info@experimenta.science" class="text-sm text-experimenta-accent hover:underline">
|
|
info@experimenta.science
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|